Day 3 談完 PPG 取出心跳間隔之後,接下來就是 HRV。這應該是穿戴裝置的恢復/壓力類指標中最常被引用的,但同時也讓人感到陌生卻又容易被誤解的指標。市面上的 app 隨便抓一個HRV數據就說「你今天恢復不佳」或是「你壓力很大」,我一直很好奇這裡面到底發生了什麼事。今天會把 HRV 拆成兩個最基本的時域指標:RMSSD 與 SDNN,它們一個看逐拍差值,一個看整體離散度,但用途完全不同。為什麼 HRV 高低不代表健康,什麼情況下 HRV 會失真,又是什麼會導致數據完全錯掉?
心率變異度(HRV)測量的不是心跳速度,而是連續心跳間期的時間變化。RMSSD 與 SDNN 雖然都是以「毫秒(ms)」為單位的時域指標,但捕捉的生理訊號與時間維度截然不同。
| 指標 | 英文全名 | 計算口徑 | 反映的變異維度 |
|---|---|---|---|
| RMSSD | Root Mean Square of Successive Differences | 相鄰 NN 間期差值(NN(i+1) − NN(i))平方和的均值開根號 | 逐拍差值(beat-to-beat):專注於相鄰心跳間的極短期、高頻劇烈擺動 |
| SDNN | Standard Deviation of NN intervals | 整段量測期間所有正常 NN 間期的標準差 | 總體離散度(global dispersion):描述整段時間內心跳間期偏離平均值的整體離散程度 |
RMSSD(副交感神經/迷走神經的即時鏡子)
心臟的迷走神經釋放乙醯膽鹼,作用時間極短(數十毫秒內即可啟動與終止),能在單次心跳之間迅速調節心率(如呼吸性竇性心律不整 RSA)。交感神經的反應慢(經由正腎上腺素傳導,需數秒才有反應),無法造成逐拍劇烈變化。因此,RMSSD 是目前臨床與穿戴裝置評估副交感(迷走神經)張力最常用的指標。
SDNN(自律神經總輸出與動態調控能力)
包含了副交感神經的高頻跳動、交感神經的慢速拉升、體溫調節、神經內分泌(如腎上腺素)以及 24 小時晝夜節律的總和。SDNN 描述的是整體自律神經系統適應內外環境變化的總儲備量,標準的臨床評估通常基於 24 小時動態心電圖(Holter)。
許多人把 HRV 當成「遊戲分數」,誤以為數值無上限衝高代表越健康,這忽略了生理與量測兩邊的限制:
一般入門消費型穿戴裝置沒有 ECG。它拿到的是 PPG 脈波的峰到峰間隔(pulse cycle interval),由此算出的變異性,正確的名稱是 PRV(pulse rate variability)。Schäfer & Vagedes 2013 回顧 PPG 與 ECG 的對照研究,結論是靜止時 PRV 的準確度足夠,但短期變異會被略微高估,來源是呼吸與心血管系統的耦合效應;而在身體活動與部分心理壓力下,PRV 與 HRV 的一致性明顯變差。
那為什麼顯示出來的還是叫 HRV?我想到兩層原因:
今天要釘的是口徑,而口徑的核心是。NN要判斷一拍從不從竇房結來,得看得到 R 波形態,手錶給不了這個。所以今天刻意用 ECG 導出的間期:先在看得見波形的資料上把定義講清楚,Day 9 再拿 PPG-DaLiA 的雙通道去量手錶那條鏈到底差多少。
PhysioNet 的 Normal Sinus Rhythm RR Interval Database(nsr2db),54 筆長時間紀錄,13 MB。檔案不是現成的間期表,而是 WFDB beat annotation(.ecg)加表頭(.hea);ANNOTATORS 寫的是 "corrected beat annotations from an automated detector"——自動偵測後再人工校正,不是原始 ECG,也不是無誤的真值。每一拍都帶 annotation code(N 竇性、V 心室早發、A 心房早發),要不要剔除是讀的人自己決定——名字裡的 RR 指的是它給你未剔除的完整序列,下面的 R-R 與 NN 兩條線都是從同一個檔案產生的。取樣率 128 Hz,間期解析度 1000/128 = 7.8125 ms,正是 Task Force 提到的 "approximately 8 ms (precisely 7.8125 ms=1/128 seconds)";nsr001 的十萬多個間期,每一個都落在這個格點上。另一半資料是合成的:變異型態已知、兩個指標可以用紙筆算出來的短序列。
SDNN 的標準差用 ddof=1。Task Force 只寫 "the square root of variance",沒指定母體或樣本,n=20 時兩者差 2.6%——這種選擇不記下來,同一個函式名就會產出兩種數字。
from wearable_ai.datasets.physionet_rr import load_rr
from wearable_ai.hrv import rmssd, sdnn
from wearable_ai.synthetic import alternating_rr, ramp_rr
alt = alternating_rr(20, mean_ms=1000, sd_ms=30) # 逐拍一長一短
ramp = ramp_rr(20, mean_ms=1000, sd_ms=30) # 單調漂移,母體 SD 相同
sdnn(alt), sdnn(ramp) # (30.78, 30.78)
rmssd(alt), rmssd(ramp) # (60.00, 5.20)
rr = load_rr("nsr001") # 所有相鄰心跳:R-R
nn = load_rr("nsr001", normal_only=True) # 兩端都是竇性搏動:NN

上圖兩條線的離散程度一樣,下圖差了一個數量級。RMSSD 看的就是下圖。
| 資料 | SDNN(ms) | RMSSD(ms) |
|---|---|---|
| 逐拍交替 ±30 ms(n=20) | 30.78 | 60.00 |
| 緩慢漂移,同樣的 SD(n=20) | 30.78 | 5.20 |
| 乾淨序列(n=300) | 20.18 | 29.36 |
| 同一條,插入一個早發拍 | 37.79 | 62.56 |
| nsr001 全段 R-R(22.55 h,106,459 個間期) | 168.65 | 34.33 |
| nsr001 全段 NN(剔掉 161 個,0.15%) | 168.54 | 32.10 |
| nsr001 的 5 分鐘視窗(270 段的中位數) | 49.66 | 25.09 |

| 原本以為 | 實際發現 |
|---|---|
| HRV 不能跨人比,是因為基因與年齡的差異 | 文獻能直接支持的是另一件事:各研究對 RR 的剔除與編修方式不同,數值本身就對不上(Nunan 2010,44 篇研究、21,438 人)。個人基準是不是更好的參考,留到 Day 15 |
| 手錶上顯示的 HRV 就是 HRV | 那是 PPG 峰間期算出來的 PRV。靜止時夠接近,但短期變異會被高估,活動與部分心理壓力下一致性明顯變差(Schäfer & Vagedes 2013) |
今天的數字都指向同一件事:一個指標值,不帶口徑就沒有意義。
hrv_rmssd_ms = 32.10 這個欄位背後至少有四個開關:間期來自 R 波還是 PPG 脈波峰、有沒有剔除非竇性搏動、視窗是 5 分鐘還是 24 小時、標準差用 ddof=0 還是 1。四個都不在欄位名裡,而幅度差很大:視窗長度能讓 SDNN 差 3.4 倍,一個沒剔掉的異常拍能讓 n=300 的 RMSSD 變 2.1 倍,ddof 的差則只有 2.6%——最小的那個一樣沒人記,而它一樣會讓兩支程式對同一段訊號給出不同的數字。LLM 不會問這是哪種口徑,它會直接拿去跟參考區間比、跟昨天比、跟別人比——今天的資料顯示,口徑不同時這三種比較全部無效。
所以 feature contract 要帶的不只是數值與單位,還有 source、window_s、beat_filter、ddof 與被剔除的比例。Task Force 1048 頁的要求就是這件事的臨床版:被剔除與內插的間期,數量與時長佔比都要一併報告。三十年前的共識文件把口徑寫進報告裡,今天的裝置把它藏在一個名詞後面。
另一件是別把變異壓成一個分數。逐拍交替與緩慢漂移的 SDNN 完全相同,生理意義卻不同,而被壓掉的正是 LLM 最需要用來判斷的結構。